/*-------------------------------------------------------------------------
 *
 * stringinfo.c
 *
 * StringInfo提供了一种可扩展的字符串数据类型（目前长度限制为1GB）。它可用于缓冲普通的C字符串（以null结尾的文本）或任意二进制数据。所有存储均通过palloc()分配（在前端代码中回退到malloc）。
 *
 * Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group
 * Portions Copyright (c) 1994, Regents of the University of California
 *
 *	  src/common/stringinfo.c
 *
 *-------------------------------------------------------------------------
 */

#ifndef FRONTEND

#include "postgres.h"
#include "utils/memutils.h"

#else

#include "postgres_fe.h"

/* 我们可能在前端代码中使用不同的值 */
#define MaxAllocSize	((Size) 0x3fffffff) /* 1 gigabyte - 1 */

#endif

#include "lib/stringinfo.h"


/*
 * makeStringInfo
 *
 * 创建一个空的 'StringInfoData' 并返回指向它的指针。
 */
StringInfo
makeStringInfo(void)
{
	StringInfo	fc_res;

	fc_res = (StringInfo) palloc(sizeof(StringInfoData));

	initStringInfo(fc_res);

	return fc_res;
}

/*
 * initStringInfo
 *
 * 初始化一个 StringInfoData 结构（具有之前未定义的内容）
 * 用于描述一个空字符串。
 */
void initStringInfo(StringInfo fc_str)
{
	int			fc_size = 1024;	/* 初始默认缓冲区大小 */

	fc_str->data = (char *) palloc(fc_size);
	fc_str->maxlen = fc_size;
	resetStringInfo(fc_str);
}

/*
 * resetStringInfo
 *
 * 重置 StringInfo：数据缓冲区保持有效，但其之前的内容（如果有）被清除。
 */
void resetStringInfo(StringInfo fc_str)
{
	fc_str->data[0] = '\0';
	fc_str->len = 0;
	fc_str->cursor = 0;
}

/*
 * appendStringInfo
 *
 * 在 fmt（一个 sprintf 风格的格式字符串）的控制下格式化文本数据
 * 并将其附加到 str 中已有的内容上。如果需要，分配更多空间
 * 给 str。这有点像 sprintf 和 strcat 的结合。
 */
void appendStringInfo(StringInfo fc_str, const char *fc_fmt,...)
{
	int			fc_save_errno = errno;

	for (;;)
	{
		va_list		fc_args;
		int			fc_needed;

		/* 尝试格式化数据。 */
		errno = fc_save_errno;
		va_start(fc_args, fc_fmt);
		fc_needed = appendStringInfoVA(fc_str, fc_fmt, fc_args);
		va_end(fc_args);

		if (fc_needed == 0)
			break;				/* 成功 */

		/* 增加缓冲区大小并重试。 */
		enlargeStringInfo(fc_str, fc_needed);
	}
}

/*
 * appendStringInfoVA
 *
 * 尝试在 fmt（一个 sprintf 风格的格式字符串）的控制下格式化文本数据
 * 并将其附加到 str 中已有的内容上。如果成功返回零；如果不成功（因为空间不足），
 * 返回所需空间的估计值，而不修改 str。通常调用者应该在重试之前将返回值
 * 传递给 enlargeStringInfo()；请参见 appendStringInfo 的标准使用模式。
 *
 * 注意：调用者必须确保在循环时保留其入口时的 errno，以防 fmt 包含 "%m"。
 *
 * XXX 这个 API 很丑陋，但考虑到 C 规范对 va_list 参数的限制，似乎没有替代方案：
 * 在重新扫描参数列表之前，必须重新执行 va_start，而我们无法从这里做到这一点。
 */
int appendStringInfoVA(StringInfo fc_str, const char *fc_fmt, va_list fc_args)
{
	int			fc_avail;
	size_t		fc_nprinted;

	Assert(fc_str != NULL);

	/*
	 * 如果几乎没有空间，就不要尝试，直接失败，让调用者先增大缓冲区。
	 * 我们必须估计增大多少，因为我们跳过了格式化工作。
	 */
	fc_avail = fc_str->maxlen - fc_str->len;
	if (fc_avail < 16)
		return 32;

	fc_nprinted = pvsnprintf(fc_str->data + fc_str->len, (size_t) fc_avail, fc_fmt, fc_args);

	if (fc_nprinted < (size_t) fc_avail)
	{
		/* 成功。注意 nprinted 不包括尾随空字符。 */
		fc_str->len += (int) fc_nprinted;
		return 0;
	}

	/* 恢复尾随的空字符，以便 str 未被修改。 */
	fc_str->data[fc_str->len] = '\0';

	/*
	 * 返回 pvsnprintf 估计的所需空间。（尽管这个值
	 * 被给出为 size_t，但我们知道它将适合 int，因为它不超过 MaxAllocSize。）
	 */
	return (int) fc_nprinted;
}

/*
 * appendStringInfoString
 *
 * 将以 null 结尾的字符串附加到 str。
 * 像 appendStringInfo(str, "%s", s) 但更快。
 */
void appendStringInfoString(StringInfo fc_str, const char *fc_s)
{
	appendBinaryStringInfo(fc_str, fc_s, strlen(fc_s));
}

/*
 * appendStringInfoChar
 *
 * 将单个字节附加到 str。
 * 像 appendStringInfo(str, "%c", ch) 但快得多。
 */
void appendStringInfoChar(StringInfo fc_str, char fc_ch)
{
	/* 如果需要，增加更多空间 */
	if (fc_str->len + 1 >= fc_str->maxlen)
		enlargeStringInfo(fc_str, 1);

	/* 好的，附加字符 */
	fc_str->data[fc_str->len] = fc_ch;
	fc_str->len++;
	fc_str->data[fc_str->len] = '\0';
}

/*
 * appendStringInfoSpaces
 *
 * 将指定数量的空格附加到缓冲区。
 */
void appendStringInfoSpaces(StringInfo fc_str, int fc_count)
{
	if (fc_count > 0)
	{
		/* 如果需要，增加更多空间 */
		enlargeStringInfo(fc_str, fc_count);

		/* 好的，附加空格 */
		while (--fc_count >= 0)
			fc_str->data[fc_str->len++] = ' ';
		fc_str->data[fc_str->len] = '\0';
	}
}

/*
 * appendBinaryStringInfo
 *
 * 将任意二进制数据附加到 StringInfo，必要时分配更多空间。
 * 确保末尾有一个空字节。
 */
void appendBinaryStringInfo(StringInfo fc_str, const char *fc_data, int fc_datalen)
{
	Assert(fc_str != NULL);

	/* 如果需要，增加更多空间 */
	enlargeStringInfo(fc_str, fc_datalen);

	/* 好的，附加数据 */
	memcpy(fc_str->data + fc_str->len, fc_data, fc_datalen);
	fc_str->len += fc_datalen;

	/*
	 * 保持一个尾随的空值，即使它对于二进制数据可能是无用的。
	 * （一些调用者正在处理文本，但由于它们的输入不是以空字符终止，因此调用这个函数。）
	 */
	fc_str->data[fc_str->len] = '\0';
}

/*
 * appendBinaryStringInfoNT
 *
 * 将任意二进制数据附加到 StringInfo，如果必要，分配更多空间。
 * 不确保存在尾随的空字节。
 */
void appendBinaryStringInfoNT(StringInfo fc_str, const char *fc_data, int fc_datalen)
{
	Assert(fc_str != NULL);

	/* 如果需要，增加更多空间 */
	enlargeStringInfo(fc_str, fc_datalen);

	/* 好的，附加数据 */
	memcpy(fc_str->data + fc_str->len, fc_data, fc_datalen);
	fc_str->len += fc_datalen;
}

/*
 * enlargeStringInfo
 *
 * 确保有足够的空间容纳“所需”的更多字节
 * （“所需”不包括终止空字符）。
 *
 * 外部调用者通常不需要担心这一点，因为
 * 所有 stringinfo.c 例程会自动处理。不过，如果调用者
 * 知道 StringInfo 最终会变得 X 字节大，
 * 它可以通过在开始存储数据之前扩大缓冲区来节省一些 palloc 的开销。
 *
 * 注意：在后端，因为我们使用 repalloc() 来扩大缓冲区，
 * 字符串缓冲区将保持在调用 initStringInfo 时的同一内存上下文中，
 * 即使现在另一个上下文是当前的。这是所需的，实际上也是至关重要的行为！
 */
void enlargeStringInfo(StringInfo str, int needed)
{
	int			fc_newlen;

	/*
	 * 防止超出范围的“所需”值。没有这个，我们可能会在以下过程中出现
	 * 溢出或无限循环。
	 */
	if (needed < 0)				/* 不应该发生 */
	{
#ifndef FRONTEND
		elog(ERROR, "invalid string enlargement request size: %d", needed);
#else
		fprintf(stderr, "invalid string enlargement request size: %d\n", needed);
		exit(EXIT_FAILURE);
#endif
	}
	if (((Size) needed) >= (MaxAllocSize - (Size) str->len))
	{
#ifndef FRONTEND
		ereport(ERROR,
				(errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
				 errmsg("out of memory"),
				 errdetail("Cannot enlarge string buffer containing %d bytes by %d more bytes.",
						   str->len, needed)));
#else
		fprintf(stderr,
				_("out of memory\n\nCannot enlarge string buffer containing %d bytes by %d more bytes.\n"),
				str->len, needed);
		exit(EXIT_FAILURE);
#endif
	}

	needed += str->len + 1;		/* 现在所需的总空间 */

	/* 由于上述测试，我们现在有所需 <= MaxAllocSize */

	if (needed <= str->maxlen)
		return;					/* 已经有足够的空间了 */

	/*
	 * 我们不想在每次附加时只分配一点更多的空间；
	 * 为了提高效率，每次溢出时将缓冲区大小加倍。
	 * 实际上，如果“所需”的大小很大，我们可能需要更多地加倍...
	 */
	fc_newlen = 2 * str->maxlen;
	while (needed > fc_newlen)
		fc_newlen = 2 * fc_newlen;

	/*
	 * 在达到 MaxAllocSize 的情况下进行限制。注意我们在这里假设
	 * MaxAllocSize <= INT_MAX/2，否则上述循环可能会
	 * 溢出。我们仍然会有 newlen >= needed。
	 */
	if (fc_newlen > (int) MaxAllocSize)
		fc_newlen = (int) MaxAllocSize;

	str->data = (char *) repalloc(str->data, fc_newlen);

	str->maxlen = fc_newlen;
}
